Skip to content

agent: @U0AJM7X8FBR Supabase - prevent API keys in Org Accounts.#13

Open
sweetmantech wants to merge 1 commit intomainfrom
agent/-u0ajm7x8fbr-supabase---preven-1773937671525
Open

agent: @U0AJM7X8FBR Supabase - prevent API keys in Org Accounts.#13
sweetmantech wants to merge 1 commit intomainfrom
agent/-u0ajm7x8fbr-supabase---preven-1773937671525

Conversation

@sweetmantech
Copy link
Copy Markdown
Contributor

@sweetmantech sweetmantech commented Mar 19, 2026

Automated PR from coding agent.

Prompt: @U0AJM7X8FBR Supabase - prevent API keys in Org Accounts.
• Actual: It's possible to create an API key for an accountID that exists in the organization_id column of the account_organization_ids table.
• Required: new migration which prevents API keys being created with account column equal to an accountID which exists in the organization_id column of the account_organization_ids table.

Summary by CodeRabbit

  • Bug Fixes
    • Added validation to prevent API key creation on organizational accounts, ensuring proper access control boundaries are maintained.

@supabase
Copy link
Copy Markdown

supabase bot commented Mar 19, 2026

Updates to Preview Branch (agent/-u0ajm7x8fbr-supabase---preven-1773937671525) ↗︎

Deployments Status Updated
Database Thu, 19 Mar 2026 16:29:16 UTC
Services Thu, 19 Mar 2026 16:29:16 UTC
APIs Thu, 19 Mar 2026 16:29:16 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations Thu, 19 Mar 2026 16:29:26 UTC
Migrations Thu, 19 Mar 2026 16:29:30 UTC
Seeding Thu, 19 Mar 2026 16:29:30 UTC
Edge Functions Thu, 19 Mar 2026 16:29:30 UTC

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 19, 2026

📝 Walkthrough

Walkthrough

A PostgreSQL migration adds a trigger function to the account_api_keys table that prevents API key creation or updates for organization accounts by checking against the account_organization_ids table and raising an exception if a match is found.

Changes

Cohort / File(s) Summary
Database Migration
supabase/migrations/20260319000000_prevent_org_account_api_keys.sql
Introduces prevent_org_account_api_keys() trigger function and associated prevent_org_account_api_keys_trigger on account_api_keys table to block API key operations for organization accounts.

Sequence Diagram

sequenceDiagram
    actor Client
    participant API as Insert/Update Request
    participant Trigger as prevent_org_account_api_keys()
    participant OrgTable as account_organization_ids
    participant APIKeysTable as account_api_keys

    Client->>API: Insert/Update API Key
    API->>Trigger: BEFORE INSERT OR UPDATE fires
    Trigger->>OrgTable: Query for organization_id = NEW.account
    OrgTable-->>Trigger: Check result
    alt Organization Account Found
        Trigger-->>API: RAISE EXCEPTION
        API-->>Client: Operation rejected
    else Non-Organization Account
        Trigger-->>APIKeysTable: Return NEW (allow operation)
        APIKeysTable-->>Client: Insert/Update succeeds
    end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A guardian trigger hops into place,
"No keys for orgs!" it declares with grace,
Organization accounts now protected with care,
This database constraint shows we're fair! 🔐

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title mentions the main change (preventing API keys in Org Accounts) but includes extraneous details like the agent mention and user reference that reduce clarity.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/-u0ajm7x8fbr-supabase---preven-1773937671525
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
supabase/migrations/20260319000000_prevent_org_account_api_keys.sql (2)

1-4: Add a follow-up data remediation for existing violating keys.

This migration blocks future writes but does not revoke/delete already-issued org-account API keys created before this fix. Consider a companion migration/job to audit and remediate existing violations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/migrations/20260319000000_prevent_org_account_api_keys.sql` around
lines 1 - 4, Add a follow-up data remediation that finds and revokes/deletes
existing API keys already issued to organization accounts (those whose
account_id appears in account_organization_ids.organization_id) — write a new
migration or one-off job that queries the api_keys table for keys where
api_keys.account_id IN (SELECT organization_id FROM account_organization_ids),
then for each matching row perform the approved remediation (e.g., set
api_keys.revoked_at = now() or delete rows), record an audit/log entry, and
ensure you run it in a transaction with a pre-check/backup step so you can
revert if needed; reference the account_organization_ids table and the api_keys
rows in your implementation.

23-25: Scope UPDATE trigger to account changes only.

The trigger currently uses BEFORE INSERT OR UPDATE, causing it to re-evaluate the organization check on any update—including changes to name, key_hash, or last_used columns. Since the function only checks NEW.account against account_organization_ids, scoping to UPDATE OF account is more efficient and prevents unnecessary evaluations:

Proposed change
 CREATE TRIGGER prevent_org_account_api_keys_trigger
-  BEFORE INSERT OR UPDATE ON public.account_api_keys
+  BEFORE INSERT OR UPDATE OF account ON public.account_api_keys
   FOR EACH ROW
   EXECUTE FUNCTION public.prevent_org_account_api_keys();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/migrations/20260319000000_prevent_org_account_api_keys.sql` around
lines 23 - 25, The trigger prevent_org_account_api_keys_trigger on
public.account_api_keys is firing for any UPDATE, but the function only
validates NEW.account; change the trigger definition so it fires BEFORE INSERT
OR UPDATE OF account (i.e., scope the UPDATE to the account column only) to
avoid unnecessary evaluations on unrelated column changes like name, key_hash,
or last_used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@supabase/migrations/20260319000000_prevent_org_account_api_keys.sql`:
- Around line 6-21: The trigger function prevent_org_account_api_keys currently
runs with default SECURITY INVOKER and will see zero rows when
account_organization_ids has RLS without policies; to fix, either convert the
function to SECURITY DEFINER and set an explicit search_path (e.g., add
"SECURITY DEFINER SET search_path = pg_catalog, public") and ensure the definer
role has SELECT rights on account_organization_ids, or keep SECURITY INVOKER but
add an RLS policy on account_organization_ids that allows SELECT for the
application roles executing the trigger; update the function declaration or RLS
policy accordingly and verify the definer/role permissions so the SELECT in
prevent_org_account_api_keys reliably sees rows.

---

Nitpick comments:
In `@supabase/migrations/20260319000000_prevent_org_account_api_keys.sql`:
- Around line 1-4: Add a follow-up data remediation that finds and
revokes/deletes existing API keys already issued to organization accounts (those
whose account_id appears in account_organization_ids.organization_id) — write a
new migration or one-off job that queries the api_keys table for keys where
api_keys.account_id IN (SELECT organization_id FROM account_organization_ids),
then for each matching row perform the approved remediation (e.g., set
api_keys.revoked_at = now() or delete rows), record an audit/log entry, and
ensure you run it in a transaction with a pre-check/backup step so you can
revert if needed; reference the account_organization_ids table and the api_keys
rows in your implementation.
- Around line 23-25: The trigger prevent_org_account_api_keys_trigger on
public.account_api_keys is firing for any UPDATE, but the function only
validates NEW.account; change the trigger definition so it fires BEFORE INSERT
OR UPDATE OF account (i.e., scope the UPDATE to the account column only) to
avoid unnecessary evaluations on unrelated column changes like name, key_hash,
or last_used.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cdcd6c5c-49bd-4504-8edd-cd5cd0e9af60

📥 Commits

Reviewing files that changed from the base of the PR and between a1726b8 and 1b1544e.

📒 Files selected for processing (1)
  • supabase/migrations/20260319000000_prevent_org_account_api_keys.sql

Comment on lines +6 to +21
CREATE OR REPLACE FUNCTION public.prevent_org_account_api_keys()
RETURNS trigger AS $$
BEGIN
IF EXISTS (
SELECT 1
FROM public.account_organization_ids
WHERE organization_id = NEW.account
) THEN
RAISE EXCEPTION
'Cannot create an API key for an organization account (account_id: %)',
NEW.account;
END IF;

RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== RLS/policies/grants touching account_organization_ids =="
rg -n -C3 'account_organization_ids|ENABLE ROW LEVEL SECURITY|CREATE POLICY|GRANT .*account_organization_ids' supabase/migrations

echo
echo "== Function security/search_path declarations =="
rg -n -C3 'prevent_org_account_api_keys|SECURITY DEFINER|search_path' supabase/migrations

Repository: recoupable/database

Length of output: 25027


🏁 Script executed:

cat -n supabase/migrations/20260319000000_prevent_org_account_api_keys.sql

Repository: recoupable/database

Length of output: 1096


🏁 Script executed:

# Search for any CREATE POLICY statements on account_organization_ids more explicitly
rg -n 'CREATE POLICY.*account_organization_ids|CREATE POLICY' supabase/migrations/20251204000000_create_account_organization_ids.sql

Repository: recoupable/database

Length of output: 45


🏁 Script executed:

# Check if there are RLS policies defined elsewhere (policies might be in separate migration)
rg -n 'account_organization_ids.*CREATE POLICY|CREATE POLICY.*account_organization_ids|ON.*account_organization_ids' supabase/migrations

Repository: recoupable/database

Length of output: 2180


🏁 Script executed:

# Check for GRANT statements on account_organization_ids
rg -n 'GRANT.*account_organization_ids|GRANT.*SELECT.*account_organization_ids' supabase/migrations

Repository: recoupable/database

Length of output: 45


🏁 Script executed:

# Check if account_organization_ids has any RLS policies defined anywhere
rg -n 'account_organization_ids' supabase/migrations | grep -i 'policy\|grant\|enabled'

Repository: recoupable/database

Length of output: 45


🏁 Script executed:

# Look for the entire content of the account_organization_ids creation migration
cat -n supabase/migrations/20251204000000_create_account_organization_ids.sql | tail -20

Repository: recoupable/database

Length of output: 1134


🏁 Script executed:

# Check if there are any RLS-related files or separate policy migrations
fd -t f 'policy\|rls' supabase/migrations/

Repository: recoupable/database

Length of output: 45


🏁 Script executed:

# Search for all CREATE POLICY statements across entire migrations directory
rg -n '^CREATE POLICY' supabase/migrations/

Repository: recoupable/database

Length of output: 435


Function will silently fail to enforce the check for non-superuser roles due to missing RLS policy coverage.

The account_organization_ids table has RLS enabled but no policies defined. Combined with SECURITY INVOKER (the default), the function's SELECT query will return zero rows when executed by an application-level authenticated role, causing the validation to always pass silently.

To fix: Either (1) add SECURITY DEFINER SET search_path = pg_catalog, public to the function and ensure the triggering role has necessary grants, or (2) define an explicit RLS policy on account_organization_ids that permits SELECT access to the roles that will execute this trigger.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/migrations/20260319000000_prevent_org_account_api_keys.sql` around
lines 6 - 21, The trigger function prevent_org_account_api_keys currently runs
with default SECURITY INVOKER and will see zero rows when
account_organization_ids has RLS without policies; to fix, either convert the
function to SECURITY DEFINER and set an explicit search_path (e.g., add
"SECURITY DEFINER SET search_path = pg_catalog, public") and ensure the definer
role has SELECT rights on account_organization_ids, or keep SECURITY INVOKER but
add an RLS policy on account_organization_ids that allows SELECT for the
application roles executing the trigger; update the function declaration or RLS
policy accordingly and verify the definer/role permissions so the SELECT in
prevent_org_account_api_keys reliably sees rows.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant